package com.jinsp.router_compiler;

import com.google.auto.service.AutoService;
import com.jinsp.router_annotation.RegisterRouter;
import com.jinsp.router_annotation.bean.RouterBean;
import com.jinsp.router_compiler.constant.APTConstant;
import com.jinsp.router_compiler.util.APTUtils;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.WildcardTypeName;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedOptions;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;

@AutoService(Processor.class)
@SupportedAnnotationTypes({"com.jinsp.router_annotation.RegisterRouter"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedOptions({"moduleName"})
public class RegisterRouterAnnotationProcessor extends AbstractProcessor {

    private Elements elements;
    private Types types;
    private Messager messager;
    private Filer filer;
    private String moduleName;          //每一个组件名

    private final Map<String, String> groupNameMap = new HashMap<>();             //组Map
    private final Map<String, List<RouterBean>> pathMap = new HashMap<>();        //路径Map

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        elements = processingEnvironment.getElementUtils();
        types = processingEnvironment.getTypeUtils();
        messager = processingEnvironment.getMessager();
        filer = processingEnvironment.getFiler();
        moduleName = processingEnvironment.getOptions().get("moduleName");
    }

    //组名：app，路径名规则：app/MainActivity

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        messager.printMessage(Diagnostic.Kind.NOTE, "开始执行RegisterRouter注解处理器");
        if (set.isEmpty()) {
            return false;
        }
        //获取支持的Activity的类型，用于校验是否标记在Activity的子类上
        TypeElement activityTypeElement = elements.getTypeElement(APTConstant.ACTIVITY_CLASS_NAME);
        TypeMirror activityTypeMirror = activityTypeElement.asType();
        //获取支持的Service的类型，用于校验是否标记在Service的子类上
        TypeElement serviceTypeElement = elements.getTypeElement(APTConstant.SERVICE_CLASS_NAME);
        TypeMirror serviceTypeMirror = serviceTypeElement.asType();
        //获取支持的v4Fragment的类型，用于校验是否标记在v4Fragment的子类上
        TypeElement v4FragmentTypeElement = elements.getTypeElement(APTConstant.FRAGMENT_V4_CLASS_NAME);
        TypeMirror v4FragmentTypeMirror = v4FragmentTypeElement.asType();
        //获取所有RegisterRouter注解标记的Element
        Set<? extends Element> elementsAnnotatedWithRegisterRouter = roundEnvironment
                .getElementsAnnotatedWith(RegisterRouter.class);
        if (elementsAnnotatedWithRegisterRouter != null) {
            for (Element element : elementsAnnotatedWithRegisterRouter) {
                //获取注解类对应的包名
                String packageName = elements.getPackageOf(element).getQualifiedName().toString();
                //获取注解标记的类名
                String className = element.getSimpleName().toString();
                messager.printMessage(Diagnostic.Kind.NOTE,
                        "获取到添加注解RegisterRouter的类：" + packageName + "." + className);
                //校验注解标记的类是否符合规定
                TypeMirror elementTypeMirror = element.asType();
                //查看是否为Activity or Service or v4Fragment类型
                boolean isActivityType = types.isSubtype(elementTypeMirror, activityTypeMirror);
                boolean isServiceType = types.isSubtype(elementTypeMirror, serviceTypeMirror);
                boolean isV4FragmentType = types.isSubtype(elementTypeMirror, v4FragmentTypeMirror);
                if (!isActivityType && !isServiceType && !isV4FragmentType) {
                    throw new RuntimeException("RegisterRouter注解目前仅作用于Activity Service v4Fragment");
                }
                //获取注解标记的类
                RegisterRouter registerRouter = element.getAnnotation(RegisterRouter.class);
                if (registerRouter != null) {
                    //获取注解上的组以及路径信息
                    String group = registerRouter.group();
                    String path = registerRouter.path();
                    if (APTUtils.isEmpty(group) && APTUtils.isEmpty(path)) {
                        //组以及路径均为空，此时采用传递过来的组名参数
                        if (APTUtils.isEmpty(moduleName)) {
                            throw new RuntimeException("组名不能为空");
                        }
                        //使用传参组名以及注解的类名进行拼接
                        group = moduleName;
                        path = moduleName + APTConstant.SEPARATOR + className;
                    } else if (APTUtils.isEmpty(path)) {
                        //组不为空但路径为空
                        path = group + APTConstant.SEPARATOR + className;
                    } else if (APTUtils.isEmpty(group)) {
                        if (path.startsWith(APTConstant.SEPARATOR)) {
                            //剔除开始字符
                            path = path.substring(1);
                        }
                        //路径不为空但组名为空，截取路径
                        String[] split = path.split(APTConstant.SEPARATOR);
                        if (split.length < 2) {
                            group = split[0];
                            //重新拼接path
                            path += (APTConstant.SEPARATOR + className);
                        } else {
                            group = split[0];
                        }
                    } else {
                        //组以及路径均不为空
                        if (path.startsWith(APTConstant.SEPARATOR)) {
                            //剔除开始字符
                            path = path.substring(1);
                        }
                    }
                    //首字符必须是字母
                    char firstChar = path.charAt(0);
                    if (!Character.isLowerCase(firstChar) && !Character.isUpperCase(firstChar)) {
                        throw new RuntimeException("路径必须是字母开头");
                    }
                    //组名必须为纯英文字母
                    if (!APTUtils.isLetterString(group)) {
                        throw new RuntimeException("组名必须全为英文字母");
                    }
                    RouterBean routerBean = new RouterBean();
                    routerBean.setElement(element);
                    routerBean.setGroupName(group);
                    routerBean.setPath(path);
                    if (isActivityType) {
                        routerBean.setType(RouterBean.RouterType.ACTIVITY);
                    }
                    if (isServiceType) {
                        routerBean.setType(RouterBean.RouterType.SERVICE);
                    }
                    if (isV4FragmentType) {
                        routerBean.setType(RouterBean.RouterType.V4FRAGMENT);
                    }
                    List<RouterBean> groutRouterBeans = pathMap.get(group);
                    if (APTUtils.isEmpty(groutRouterBeans)) {
                        groutRouterBeans = new ArrayList<>();
                        groutRouterBeans.add(routerBean);
                        pathMap.put(group, groutRouterBeans);
                    } else {
                        groutRouterBeans.add(routerBean);
                    }
                }
            }
            if (!APTUtils.isEmpty(pathMap)) {
                //获取路径接口描述
                TypeElement pathTypeElement = elements.getTypeElement(APTConstant.ROUTER_API_PATH);
                //生成路径对应的文件
                try {
                    createPathFile(pathTypeElement);
                    if (!APTUtils.isEmpty(groupNameMap)) {
                        //获取组接口描述
                        TypeElement groupTypeElement = elements.getTypeElement(APTConstant.ROUTER_API_GROUP);
                        //生成组对应的文件
                        createGroupFile(groupTypeElement, pathTypeElement);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return false;
    }

    /**
     * 创建路由表组对应的文件
     *
     * @param groupTypeElement 组描述
     * @param pathTypeElement  路径描述
     * @throws IOException 写入异常
     */
    private void createGroupFile(TypeElement groupTypeElement, TypeElement pathTypeElement) throws IOException {
        //构建返回类型Map<String, Class<? extends IRouterPath>>
        TypeName returnTypeName = ParameterizedTypeName.get(ClassName.get(Map.class),
                ClassName.get(String.class),
                ParameterizedTypeName.get(ClassName.get(Class.class),
                        WildcardTypeName.subtypeOf(ClassName.get(pathTypeElement))));
        //构建方法public Map<String, Class<? extends IRouterPath>> getGroupMap()
        MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(APTConstant.ROUTER_API_GROUP_METHOD_NAME)
                .addAnnotation(Override.class)
                .addModifiers(Modifier.PUBLIC)
                //Map<String, Class<? extends IRouterPath>> groupMap = new HashMap<>();
                .addStatement("$T<$T, $T> $N = new $T<>()", ClassName.get(Map.class),
                        ClassName.get(String.class),
                        ParameterizedTypeName.get(ClassName.get(Class.class),
                                WildcardTypeName.subtypeOf(ClassName.get(pathTypeElement))),
                        APTConstant.ROUTER_API_GROUP_MAP_VARIABLE_NAME, ClassName.get(HashMap.class))
                .returns(returnTypeName);
        String groupName = null;
        for (Map.Entry<String, String> entry : groupNameMap.entrySet()) {
            if (APTUtils.isEmpty(groupName)) {
                groupName = entry.getKey();
            }
            //groupMap.put(group, IRouterPath.class);
            methodBuilder.addStatement("$N.put($S, $T.class)",
                    APTConstant.ROUTER_API_GROUP_MAP_VARIABLE_NAME,
                    groupName, ClassName.get(APTConstant.ROUTER_FILE_PACKAGE_NAME, entry.getValue()));
        }
        methodBuilder.addStatement("return $N", APTConstant.ROUTER_API_GROUP_MAP_VARIABLE_NAME);
        //前缀加组名生成文件
        String groupClassName = APTConstant.GROUP_CLASS_NAME_PREFIX + groupName;
        messager.printMessage(Diagnostic.Kind.NOTE,
                "开始创建Group类：" + APTConstant.ROUTER_FILE_PACKAGE_NAME + "." + groupClassName);
        //写入组文件
        JavaFile.builder(APTConstant.ROUTER_FILE_PACKAGE_NAME,
                TypeSpec.classBuilder(groupClassName)
                        .addSuperinterface(ClassName.get(groupTypeElement))
                        .addModifiers(Modifier.PUBLIC)
                        .addMethod(methodBuilder.build())
                        .build())
                .build()
                .writeTo(filer);
    }

    /**
     * 创建路由表路径对应的文件
     *
     * @param pathTypeElement 路径接口描述
     * @return 包名
     * @throws IOException 写入异常
     */
    private void createPathFile(TypeElement pathTypeElement) throws IOException {
        //构建返回类型Map<String, RouterBean>
        TypeName returnTypeName = ParameterizedTypeName.get(ClassName.get(Map.class),
                ClassName.get(String.class), ClassName.get(RouterBean.class));
        //构建方法public Map<String, RouterBean> getPathMap()
        MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(APTConstant.ROUTER_API_PATH_METHOD_NAME)
                .addAnnotation(Override.class)
                .addModifiers(Modifier.PUBLIC)
                //Map<String, RouterBean> pathMap = new HashMap<>();
                .addStatement("$T<$T, $T> $N = new $T<>()", ClassName.get(Map.class),
                        ClassName.get(String.class), ClassName.get(RouterBean.class),
                        APTConstant.ROUTER_API_PATH_MAP_VARIABLE_NAME, ClassName.get(HashMap.class))
                .returns(returnTypeName);
        for (Map.Entry<String, List<RouterBean>> entry : pathMap.entrySet()) {
            List<RouterBean> routerBeans = entry.getValue();
            for (RouterBean routerBean : routerBeans) {
                //pathMap.put(path, RouterBean.create(path, group, xxx.class, RouterBean.RouterType.xxx));
                methodBuilder.addStatement("$N.put($S, $T.create($S, $S, $T.class, $T.$L))",
                        APTConstant.ROUTER_API_PATH_MAP_VARIABLE_NAME, routerBean.getPath(),
                        ClassName.get(RouterBean.class), routerBean.getPath(),
                        routerBean.getGroupName(), ClassName.get((TypeElement) routerBean.getElement()),
                        ClassName.get(RouterBean.RouterType.class), routerBean.getType());
            }
            methodBuilder.addStatement("return $N", APTConstant.ROUTER_API_PATH_MAP_VARIABLE_NAME);
            //前缀加组名生成文件
            String pathClassName = APTConstant.PATH_CLASS_NAME_PREFIX + entry.getKey();
            messager.printMessage(Diagnostic.Kind.NOTE,
                    "开始创建Path类：" + APTConstant.ROUTER_FILE_PACKAGE_NAME + "." + pathClassName);
            //写入路径文件
            JavaFile
                    .builder(APTConstant.ROUTER_FILE_PACKAGE_NAME,
                            //public class Router_PathClassName_group implements IRouterPath
                            TypeSpec.classBuilder(pathClassName)
                                    .addSuperinterface(ClassName.get(pathTypeElement))
                                    .addModifiers(Modifier.PUBLIC)
                                    .addMethod(methodBuilder.build())
                                    .build())
                    .build()
                    .writeTo(filer);
            //保存生成的路径文件
            groupNameMap.put(entry.getKey(), pathClassName);
        }
    }
}